<html><head><meta charset="utf-8"/><title>map／reduce</title></head><body><h1>map／reduce</h1><div class="x-wiki-content x-main-content">
 <p>
  如果你读过Google的那篇大名鼎鼎的论文“
  <a href="http://research.google.com/archive/mapreduce.html">
   MapReduce: Simplified Data Processing on Large Clusters
  </a>
  ”，你就能大概明白map/reduce的概念。
 </p>
 <h3>
  map
 </h3>
 <p>
  举例说明，比如我们有一个函数f(x)=x
  <sup>
   2
  </sup>
  ，要把这个函数作用在一个数组
  <code>
   [1, 2, 3, 4, 5, 6, 7, 8, 9]
  </code>
  上，就可以用
  <code>
   map
  </code>
  实现如下：
 </p>
 <p>
  <img alt="map" data-src="/files/attachments/925425803658112/0" src="https://www.liaoxuefeng.com/files/attachments/925425803658112/0"/>
 </p>
 <p>
  由于
  <code>
   map()
  </code>
  方法定义在JavaScript的
  <code>
   Array
  </code>
  中，我们调用
  <code>
   Array
  </code>
  的
  <code>
   map()
  </code>
  方法，传入我们自己的函数，就得到了一个新的
  <code>
   Array
  </code>
  作为结果：
 </p>
 <pre><code class="language-x-javascript">'use strict';

function pow(x) {
    return x * x;
}
----
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
console.log(results);
</code></pre>
 <p>
  注意：
  <code>
   map()
  </code>
  传入的参数是
  <code>
   pow
  </code>
  ，即函数对象本身。
 </p>
 <p>
  你可能会想，不需要
  <code>
   map()
  </code>
  ，写一个循环，也可以计算出结果：
 </p>
 <pre><code>var f = function (x) {
    return x * x;
};

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var result = [];
for (var i=0; i&lt;arr.length; i++) {
    result.push(f(arr[i]));
}
</code></pre>
 <p>
  的确可以，但是，从上面的循环代码，我们无法一眼看明白“把f(x)作用在Array的每一个元素并把结果生成一个新的Array”。
 </p>
 <p>
  所以，
  <code>
   map()
  </code>
  作为高阶函数，事实上它把运算规则抽象了，因此，我们不但可以计算简单的f(x)=x
  <sup>
   2
  </sup>
  ，还可以计算任意复杂的函数，比如，把
  <code>
   Array
  </code>
  的所有数字转为字符串：
 </p>
 <pre><code>var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
</code></pre>
 <p>
  只需要一行代码。
 </p>
 <h3>
  reduce
 </h3>
 <p>
  再看reduce的用法。Array的
  <code>
   reduce()
  </code>
  把一个函数作用在这个
  <code>
   Array
  </code>
  的
  <code>
   [x1, x2, x3...]
  </code>
  上，这个函数必须接收两个参数，
  <code>
   reduce()
  </code>
  把结果继续和序列的下一个元素做累积计算，其效果就是：
 </p>
 <pre><code>[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
</code></pre>
 <p>
  比方说对一个
  <code>
   Array
  </code>
  求和，就可以用
  <code>
   reduce
  </code>
  实现：
 </p>
 <pre><code>var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x + y;
}); // 25
</code></pre>
 <p>
  练习：利用
  <code>
   reduce()
  </code>
  求积：
 </p>
 <pre><code class="language-x-javascript">'use strict';

function product(arr) {
----
    return 0;
----
}

// 测试:
if (product([1, 2, 3, 4]) === 24 &amp;&amp; product([0, 1, 2]) === 0 &amp;&amp; product([99, 88, 77, 66]) === 44274384) {
    console.log('测试通过!');
}
else {
    console.log('测试失败!');
}
</code></pre>
 <p>
  要把
  <code>
   [1, 3, 5, 7, 9]
  </code>
  变换成整数13579，
  <code>
   reduce()
  </code>
  也能派上用场：
 </p>
 <pre><code>var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x * 10 + y;
}); // 13579
</code></pre>
 <p>
  如果我们继续改进这个例子，想办法把一个字符串
  <code>
   13579
  </code>
  先变成
  <code>
   Array
  </code>
  ——
  <code>
   [1, 3, 5, 7, 9]
  </code>
  ，再利用
  <code>
   reduce()
  </code>
  就可以写出一个把字符串转换为Number的函数。
 </p>
 <p>
  练习：不要使用JavaScript内置的
  <code>
   parseInt()
  </code>
  函数，利用map和reduce操作实现一个
  <code>
   string2int()
  </code>
  函数：
 </p>
 <pre><code class="language-x-javascript">'use strict';

function string2int(s) {
----
    return 0;
----
}

// 测试:
if (string2int('0') === 0 &amp;&amp; string2int('12345') === 12345 &amp;&amp; string2int('12300') === 12300) {
    if (string2int.toString().indexOf('parseInt') !== -1) {
        console.log('请勿使用parseInt()!');
    } else if (string2int.toString().indexOf('Number') !== -1) {
        console.log('请勿使用Number()!');
    } else {
        console.log('测试通过!');
    }
}
else {
    console.log('测试失败!');
}
</code></pre>
 <h3>
  练习
 </h3>
 <p>
  请把用户输入的不规范的英文名字，变为首字母大写，其他小写的规范名字。输入：
  <code>
   ['adam', 'LISA', 'barT']
  </code>
  ，输出：
  <code>
   ['Adam', 'Lisa', 'Bart']
  </code>
  。
 </p>
 <pre><code class="language-x-javascript">'use strict';

function normalize(arr) {
----
    return [];
----
}

// 测试:
if (normalize(['adam', 'LISA', 'barT']).toString() === ['Adam', 'Lisa', 'Bart'].toString()) {
    console.log('测试通过!');
}
else {
    console.log('测试失败!');
}
</code></pre>
 <p>
  小明希望利用
  <code>
   map()
  </code>
  把字符串变成整数，他写的代码很简洁：
 </p>
 <pre><code class="language-x-javascript">'use strict';

var arr = ['1', '2', '3'];
var r;
----
r = arr.map(parseInt);
----
console.log(r);
</code></pre>
 <p>
  结果竟然是
  <code>
   1, NaN, NaN
  </code>
  ，小明百思不得其解，请帮他找到原因并修正代码。
 </p>
 <p>
  提示：参考
  <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">
   Array.prototype.map()的文档
  </a>
  。
 </p>
 <p>
  <button class="uk-button uk-button-success" id="x-why-parseInt-failed">
   原因分析
  </button>
 </p>
 <div id="x-answer-parseInt-failed" style="display:none;">
  <p>
   由于
   <code>
    map()
   </code>
   接收的回调函数可以有3个参数：
   <code>
    callback(currentValue, index, array)
   </code>
   ，通常我们仅需要第一个参数，而忽略了传入的后面两个参数。不幸的是，
   <code>
    parseInt(string, radix)
   </code>
   没有忽略第二个参数，导致实际执行的函数分别是：
  </p>
  <ul>
   <li>
    <p>
     parseInt('1', 0); // 1, 按十进制转换
    </p>
   </li>
   <li>
    <p>
     parseInt('2', 1); // NaN, 没有一进制
    </p>
   </li>
   <li>
    <p>
     parseInt('3', 2); // NaN, 按二进制转换不允许出现3
    </p>
   </li>
  </ul>
  <p>
   可以改为
   <code>
    r = arr.map(Number);
   </code>
   ，因为
   <code>
    Number(value)
   </code>
   函数仅接收一个参数。
  </p>
 </div>
 <script>
  $(function () {
    $('#x-why-parseInt-failed').click(function () {
        var btn = $(this);
        btn.attr('disabled', 'disabled');
        btn.text('请先思考60秒...');
        setTimeout(function () {
            $('#x-why-parseInt-failed').hide();
            $('#x-answer-parseInt-failed').show();
        }, 60000);
    });
});
 </script>
</div>
</body></html>